/*********************************************************************************************************************
 * The SNIFFER Packet Operations Library.
 * -------------------------------------
 *
 * Carlos Nunez		(cnunez@stevens.edu)
 * Kevin Fuhrman	(ktfuhrman@gmail.com)
 * William Best		(wbest@stevens.edu)
 * Mike DiAmore		(mdiamore@stevens.edu)
 *
 * Created on: 	10 April, 2010
 * Last edited:
 *
 * Description
 * -----------
 * This header file describes all of the packet header parsers available for SNIFFER
 * as well as SNIFFER's main packet reader. The function of the packet reader is as
 * follows:
 *
 * 	a) pcap_loop(...) opens read_packet(...) for processing.
 *  b) read_packet(...) parses each header type in sequential order using the individual header parsers defined here.
 *  c) It ends by printing IP source/destination and protocol type to console or GUI.
 *
 *  For more information on the protocols supported by SNIFFER, please read the protocols.txt
 *  file located at the root of the source directory.
 *
 *********************************************************************************************************************/

//-------------------------
//	Library includes.
//-------------------------
#include "sniffer_handlers.hpp"
#include "gui/sniffer_gtk_gui.h"

using namespace std;

//-------------------------------------------
//	Global variables (pkt packet properties
//-------------------------------------------

const char *	pkt_time;

const char *	eth_src;
const char *	eth_dst;
const char *	eth_type;

const char *	ip_src;
const char *	ip_dst;
const char *	ip_type;

//-----------------------------------------
//	Internal methods.
//-----------------------------------------

void	get_arp_addr(char buf[], const char *arp_buf, const char *mode);
void	props_write(string msg, int level);			//Properties string handler.

//-----------------------------------------
//	Globals
//-----------------------------------------

string			packet_props;
boost::mutex	io_mutex;			//Mutex for props_write; avoids potential corrupted writes (very unlikely).

/*******************************************************************

capture_packet(u_char *, const struct pcap_pkthdr *, const u_char *)
--------------------------------------------------------------------
Description:
	Handles pcap packets and parses them accordingly.

*******************************************************************/

void capture_packet(u_char * args, const struct pcap_pkthdr *pkthdr, const u_char * packet)
{
	//Set the packet ID.
	//------------------
	static int pkt_id = 0;
	sniffer_pkt::Instance()->set_pkt_id(itoch(pkt_id));
	
	//Start packet parsing.
	//---------------------	
	parse_ether_header(packet,ctime((const time_t *) &pkthdr->ts.tv_sec),pkt_id);
	
	//Set properties string.
	//---------------------
	sniffer_pkt::Instance()->set_a_info(packet_props);
	
	//Print the information.
	//----------------------
	if (etheraddr_print_enabled())
	{
		if (!is_gui_running())
			print_packet("eth");
		else
			addToPacketList();
	}
	else 
	{
		if (!is_gui_running())
			print_packet("ip");
		else
			addToPacketList();
	}
	
	//Continue.
	//--------
	pkt_id++;
	packet_props = "";
	sniffer_pkt::Instance()->clr_sniffer_pkt();

}

//============================	LAYER 2 PARSERS	  =======================================//

/*******************************************************************

void parse_ether_header(const u_char *packet)
--------------------------------------------------------------------
Description:
	Reads and parses Ethernet headers.

*******************************************************************/

void parse_ether_header(const u_char *packet, const char *time, u_int pkt_id)
{
	//Get ethernet frame information.
	//-------------------------------
	struct ether_header *header;
	header	=	(struct ether_header *) packet;

	//Pre-allocate space for the source and destination address.
	//----------------------------------------------------------
	//Store the information.
	//----------------------
	u_short		pkt_type	=	header->ether_type;
	pkt_time	=	(const char *) time;
	
	string pkt_src(ether_ntoa( (const struct ether_addr *) &header->ether_shost) );
	string pkt_dst(ether_ntoa( (const struct ether_addr *) &header->ether_dhost) );
	string pkt_type_str;
	
	//Set source and destination in sniffer pkt
	//-----------------------------------------
	sniffer_pkt::Instance()->set_src_mac(pkt_src.c_str());
	sniffer_pkt::Instance()->set_dst_mac(pkt_dst.c_str());
	
	//Determine type.
	//---------------
	switch(r_ntohs(pkt_type))
	{
		case(ETHERTYPE_IP):
				pkt_type_str = "IPv4";
				sniffer_pkt::Instance()->set_pkt_type(pkt_type_str.c_str());
				parse_ipv4_datagram(packet);			
			
				break;
		case(ETHERTYPE_IPV6):
				pkt_type_str = "IPv6";
				sniffer_pkt::Instance()->set_pkt_type(pkt_type_str.c_str());
				parse_ipv6_datagram(packet);
			
				break;
		case(ETHERTYPE_ARP):
				pkt_type_str = "ARP";
				sniffer_pkt::Instance()->set_pkt_type(pkt_type_str.c_str());
				
				parse_arp_header(packet);			//This parser will take care of further delegation.
				break;
		case(ETHERTYPE_REVARP):
				pkt_type_str = "Reverse ARP";
				sniffer_pkt::Instance()->set_pkt_type(pkt_type_str.c_str());
			
				parse_arp_header(packet);			//This parser will take care of further delegation.
				break;
		default:
				pkt_type_str = "?";
				sniffer_pkt::Instance()->set_pkt_type(pkt_type_str.c_str());
				break;
	}
	
}

/******************************************************************
 void parse_ipv4_datagram(const u_char *packet)
 --------------------------------------------
 
	Prints and stores information on IPv4 datagrams.
 
 ******************************************************************/

void parse_ipv4_datagram(const u_char *packet)
{
	props_write("IPv4",0);		//Start the properties string.
	
	//Get IP datagram.
	//----------------
	struct ip *datagram;
	datagram = (struct ip *) (packet + sizeof(struct ether_header));
	
	//Assign key properties.
	//----------------------
	string pkt_src(inet_ntoa(datagram->ip_src));
	string pkt_dst(inet_ntoa(datagram->ip_dst));
	
	//Store.
	//-----
	sniffer_pkt::Instance()->set_src_ip(pkt_src);
	sniffer_pkt::Instance()->set_dst_ip(pkt_dst);
	
	//----------------------------------------------------------------------------------
	//	Construct string.
	//----------------------------------------------------------------------------------
	
	//Store datagram IP version and header length. differs by OS
	//----------------------------------------------------------
#if defined(WIN32) || defined(_WIN32)
	u_int ip_v	=	IP_VHL_V(datagram->ip_vhl);
	u_int ip_hl =	IP_VHL_HL(datagram->ip_vhl);
#else
	u_int ip_v 	=	datagram->ip_v;
	u_int ip_hl	=	datagram->ip_hl;
#endif

	
	//Version and header length
	//--------------------------
	props_write("Version: "			+ itoch(ip_v),1);
	props_write("Header length: "	+ itoch(ip_hl),1);
	
	//Type of service
	//---------------
	switch (datagram->ip_tos)
	{
		case (IPTOS_LOWDELAY):	props_write("TOS: Low Delay",1); break;
		case(IPTOS_THROUGHPUT): props_write("TOS: Optimized for Throughput",1); break;
		case(IPTOS_RELIABILITY):props_write("TOS: Optimized for Reliability",1); break;
		case(IPTOS_MINCOST):	props_write("TOS: Minimum cost",1); break;
		default:				props_write("TOS: Not defined",1); break;
	}
	
	//----------------------
	//	Fragment logic
	//----------------------
	
	//Identification
	//--------------	
	props_write("Identification: " + itoch(r_ntohs(datagram->ip_id)),1);
	
	//TTL
	//---
	props_write("TTL: " + itoch( (int)datagram->ip_ttl ),1);
	
	//Protocol
	//--------
	struct protoent * ip_proto	= getprotobynumber(datagram->ip_p);
	props_write("Protocol: " + itoch( (int)datagram->ip_p ) + "(" + (string) ip_proto->p_name +")",1);
	
	//IPs
	//------
	props_write("Source: " + pkt_src,1);
	props_write("Destination: " + pkt_dst,1);
	
	//Get fragment offset.
	//--------------------
	u_short ip_off	=	r_ntohs(datagram->ip_off);
	u_short	offset	=	ip_off & IP_OFFMASK;
	props_write("Fragment offset: " + itoch(offset),1);
	
	if (ip_off & IP_MF)		//If fragoff is zero and MF is set, more are coming...
	{
		props_write("Flags: 0x4000 (MF)",2);
		props_write("Length: " + itoch( r_ntohs( datagram->ip_len ) - r_ntohs( ip_hl )),2);
	}
	else if (offset != 0)
	{
		props_write("Flags: 0x0000 (FF)",2);
		props_write("Length: " + itoch(r_ntohs(datagram->ip_len) - r_ntohs(ip_hl)),2);
	}
	else {
		//Determine parser to go to next.
		//------------------------------
		switch (datagram->ip_p)
		{
			case (IPPROTO_TCP):		parse_tcp_header(packet);	break;
			case (IPPROTO_UDP):		parse_udp_header(packet);	break;
			default:				props_write("Transport protocol not supported by SNIFFER.",1);	break;
		}
	}
}

/******************************************************************
 void parse_ipv6_datagram(const u_char *packet)
 --------------------------------------------
 
 Prints and stores information on IPv6 datagrams.
 
 ******************************************************************/
void parse_ipv6_datagram(const u_char *packet)
{
	props_write("IPv6",0);		//Let's get it there.
	
	//Get IP datagram.
	//----------------
	struct ip6_hdr	*datagram;
	datagram	=	(struct ip6_hdr *) (packet + sizeof(struct ether_header));
	
	//Get source and destination addresses
	//------------------------------------
	char src_addr[INET6_ADDRSTRLEN];																	//Source
	string pkt_src(inet_ntop(AF_INET6, &(datagram->ip6_src), src_addr, INET6_ADDRSTRLEN));
	
	char dst_addr[INET6_ADDRSTRLEN];																	//Dest
	string pkt_dst(inet_ntop(AF_INET6, &(datagram->ip6_dst), dst_addr, INET6_ADDRSTRLEN));	
	
	//Store.
	//-----
	sniffer_pkt::Instance()->set_src_ip(pkt_src);
	sniffer_pkt::Instance()->set_dst_ip(pkt_dst);	
	
	//Collect information.
	//------------------------------------------
	
}

/***************************************************************
 
 void parse_tcp_header(const u_char *packet)
 -------------------------------------------
 Parses TCP header.
 
 ****************************************************************/

void parse_tcp_header(const u_char *packet)
{
	struct tcphdr *tcp_hdr;
	tcp_hdr = (struct tcphdr *)(packet + sizeof(struct ether_header) + sizeof(struct ip));
	
	props_write("TCP",0);			//Start the properties string.
	
	//Set
	//----
	u_short	th_sport	=	r_ntohs(tcp_hdr->th_sport);			//source port
	u_short	th_dport	=	r_ntohs(tcp_hdr->th_dport);			//dest port
	u_int	th_seq		=	ntohl(tcp_hdr->th_seq);				//Sequence #
	u_int	th_ack		=	ntohl(tcp_hdr->th_ack);				//Acknowledgement #
	int		th_off		=	tcp_hdr->th_off;					//Offset
	u_char	th_flags	=	tcp_hdr->th_flags;					//Status flags
	u_short	th_win		=	tcp_hdr->th_win;					//Window size.
	u_short th_sum		=	tcp_hdr->th_sum;					//Checksum.
	u_short	th_urp		=	tcp_hdr->th_urp;					//Urgent pointer.
	
		
	
	//Ports
	//-----
	props_write("Source Port: " + itoch(th_sport),1);
	props_write("Destination Port: " + itoch(th_dport),1);
	
	//Seq-ack
	//-------
	props_write("Sequence Number: " + itoch(th_seq),1);
	props_write("Acknowledge Number: " + itoch(th_ack),1);
	
	//Length
	//------
	props_write("Header length: " + itoch(th_off),1);
	
	//Control
	//-------
	string	th_flags_str;						//Flags string.
	th_flags_str.clear();
	th_flags_str	=	"Flags: ";			
	
	
	//------------------------------------------------------
	//The only valid TCP states are:
	//	SYN (TCP connect open req)		0x02
	//	ACK (TCP acknowledgement)		0x10
	//	SYN-ACK (TCP handshake)			0x12
	//	FIN	(TCP connect close req)		0x01
	//	FIN-ACK (TCP close ack)			0x11
	//	RST	(TCP connection reset)		0x04
	//------------------------------------------------------
	
	if		(th_flags & TH_SYN)
		th_flags_str.append("0x02 (SYN)");
	else if	(th_flags & TH_ACK)
		th_flags_str.append("0x10 (ACK)");
	else if (th_flags & (TH_SYN | TH_ACK))
		th_flags_str.append("0x12 (SYN,ACK)");
	else if (th_flags & TH_FIN)
		th_flags_str.append("0x01 (FIN)");
	else if (th_flags & (TH_FIN | TH_ACK))
		th_flags_str.append("0x11 (FIN,ACK)");
	else if (th_flags & TH_RST)
		th_flags_str.append("0x04 (RST)");
	else
		th_flags_str.append("Other flags set");
	
	//Write to props
	//--------------
	props_write(th_flags_str,1);
	
	props_write("Window size: " + itoch(th_win),1);		//Window size.
	props_write("Checksum: " + itoch(th_sum,hex),1);			//Checksum.
	
	//Print urgent pointer offset if set.
	//-----------------------------------
	if (th_flags & TH_URG)
		props_write("Urgent pointer offset: " + th_urp,1);
	 
		
}

/***************************************************************
 
 void parse_udp_header(const u_char *packet)
 -------------------------------------------
 Parses UP header.
 
 ****************************************************************/

void parse_udp_header(const u_char *packet)
{
	struct udphdr *udp_hdr;
	udp_hdr	= (struct udphdr *)(packet + sizeof(struct ether_header) + sizeof(struct ip));
	props_write("UDP",0);			//Start it off.
	
	//Add
	//----
	props_write("Source Port: " + itoch(r_ntohs(udp_hdr->uh_sport)),1);
	props_write("Destination Port: " + itoch(r_ntohs(udp_hdr->uh_dport)),1);
	props_write("Length: " + itoch(r_ntohs(udp_hdr->uh_ulen)),1);
	props_write("Checksum: " + itoch(r_ntohs(udp_hdr->uh_sum)),1);
	
	//----------------------------------------------------------------------------------------
	//	Higher level protocol handling
	//----------------------------------------------------------------------------------------
	
	//DNS
	//----
	if (r_ntohs(udp_hdr->uh_sport) == 53 || r_ntohs(udp_hdr->uh_dport) == 53)		
	{
		sniffer_pkt::Instance()->set_pkt_type("DNS");
		parse_dns_header(packet);
	}
	else 
		sniffer_pkt::Instance()->set_pkt_type("Other");
}

/***************************************************************
 
void parse_arp_header(const u_char *packet)
-------------------------------------------
	Parses ARP header.
 
****************************************************************/

void parse_arp_header(const u_char *packet)
{
#if defined(WIN32) || defined(_WIN32)
	struct sniffer_arphdr *header; 
	header = (struct sniffer_arphdr *) (packet + sizeof(struct ether_header));
#else
	struct arphdr *header;
	header = (struct arphdr *) (packet + sizeof(struct ether_header));
#endif

	//Source and target MAC addresses.
	//--------------------------------
	string src_mac( ether_ntoa( (const struct ether_addr *) SHA(header) ) );
	string trg_mac( ether_ntoa( (const struct ether_addr *) THA(header) ) );
	sniffer_pkt::Instance()->set_src_mac(src_mac);
	sniffer_pkt::Instance()->set_dst_mac(trg_mac);

	//Now determine protocol.
	//-----------------------
	string src_ip, trg_ip;

	if (r_ntohs(header->ar_pro) == ETHERTYPE_IP)
	{		
		src_ip = inet_ntoa(*(struct in_addr *)SPA(header));
		trg_ip = inet_ntoa(*(struct in_addr *)TPA(header));
	}

	//Write to packet.
	//----------------
	sniffer_pkt::Instance()->set_src_ip(src_ip);
	sniffer_pkt::Instance()->set_dst_ip(trg_ip);

	//Determine operation.
	//--------------------
	switch (htons(header->ar_op))
	{
		case(ARPOP_REQUEST):
			sniffer_pkt::Instance()->set_g_info("Who has " + trg_ip + "? Tell " + src_ip);
			break;
			
		case(ARPOP_REPLY):
			sniffer_pkt::Instance()->set_g_info(trg_ip + " has " + src_ip);
			break;
			
		case(ARPOP_REVREQUEST):
			sniffer_pkt::Instance()->set_g_info("Who has " + src_mac + "? Tell " + trg_mac);
			break;
			
		case(ARPOP_REVREPLY):
			sniffer_pkt::Instance()->set_g_info(trg_mac + " has " + src_mac);
			break;
	}
}

		
/**/


//==================================================	APPLICATION LAYER	=============================================================//

/***************************************************
 void parse_dns_header(const u_char *packet)
 -------------------------------------------
	Parses DNS requests and responses.
 
 ****************************************************/

void parse_dns_header(const u_char *packet)
{
	//Get the payload.
	//----------------
	const u_char			* payload	=	(const u_char *)(packet + sizeof(struct ether_header) + sizeof(struct ip) + sizeof(struct udphdr));
	
	//Create the DNS struct
	//---------------------
	struct sniffer_dnshdr * header = ( (struct sniffer_dnshdr *)  payload );
	
	//DEBUG: See some values in memory
	//--------------------------------
	
	props_write("DNS",0);			//Let's get it there.
	
	//Store data
	//----------
	u_short	qid		=	r_ntohs(header->qid);
	u_short flags	=	r_ntohs(header->flags);
	u_short	qdcount	=	r_ntohs(header->qdcount);
	u_short	ancount	=	r_ntohs(header->ancount);
	u_short nscount	=	r_ntohs(header->nscount);
	u_short	arcount	=	r_ntohs(header->arcount);
	
	//FLAGS
	//-----
	u_char	qr		=	DNS_FLAGS_QR(flags);
	u_char	opcode	=	DNS_FLAGS_OPCODE(flags);
	u_char	aa		=	DNS_FLAGS_AA(flags);
	u_char	tc		=	DNS_FLAGS_TC(flags);
	u_char	rd		=	DNS_FLAGS_RD(flags);
	u_char	ra		=	DNS_FLAGS_RA(flags);
	u_char	rcode	=	DNS_FLAGS_RCODE(flags);
	
	//DNS QID
	//-------
	props_write("QID: 0x" + itoch(qid, hex),1);
	
	string flags_str = print_bitstring(flags);
	
	//---------------------------------	FLAG HANDLING ----------------------------------------------------//
	
	//Prepare flags.
	//--------------
	props_write("Flags: 0x" + itoch(flags, hex) + "(" + print_bitstring(flags) + ")",1);
	
	if (!qr) 
		props_write("QR: Query",2);
	else
		props_write("QR: Response",2);			//Determine if qry or response using QR
	
	switch (opcode)
	{
		case (DNS_STD_QRY):			props_write("OP: Standard Query (" + print_bitstring(opcode) + ")",2);	break;
		case (DNS_INV_QRY):			props_write("OP: Inverse Query (" + print_bitstring(opcode) + ")",2);	break;
		case (DNS_STATUS_REQ):		props_write("OP: Status Request (" + print_bitstring(opcode) + ")",2);	break;
	}
	
	
	if (aa)		props_write("AA: From authoritative server",2);		else props_write("AA: Not from an authoritative server",2);
	if (tc)		props_write("TC: Truncated",2);						else props_write("TC: Full message",2);
	if (rd)		props_write("RD: Recursive search requested",2);	else props_write("RD: Recursive search NOT requested",2);
	if (!ra)		props_write("RA: Recursve search not supported",2);	else props_write("RA: Recursive search supported",2);

	switch (rcode)
	{
		case (DNS_QRY_GOOD):		props_write("RCODE: Query successful",2);			break;
		case (DNS_QRY_ERR):			props_write("RCODE: Query format error",2);			break;
		case (DNS_SERVER_FAIL):		props_write("RCODE: Nameserver not available",2);	break;
		case (DNS_QRY_REJ):			props_write("RCODE: Query rejected",2);				break;
	}
	
	//--------------------------------------- END FLAG HANDLING ----------------------------------------//
	
	props_write("Queries: " + itoch(qdcount),1);
	props_write("Responses: " + itoch(ancount),1);
	props_write("Authority Nameservers: " + itoch(nscount),1);
	props_write("Additional Records: " + itoch(arcount),1);
}


/***************************************************
 void parse_text_payload(const char *packet, const char *proto)
 --------------------------------------------------------------
	Some protocols (HTTP, FTP) have a clear-text
	payload, so this method simply dumps whatever
	it contains.
 ****************************************************/

void parse_text_payload(const u_char *packet, const char *proto)
{
	const u_char * payload	=	(const u_char *)(packet + sizeof(struct ether_header) + sizeof(struct ip) + sizeof(struct udphdr));		//Get the payload
	string payload_str(reinterpret_cast<const char *>(payload));

	//Display payload
	//---------------
	props_write(proto,1);
	props_write(payload_str,2);

}


//==================================================		SUPPORT			=============================================================//
/***************************************************
 void write(string msg, int level)
 ---------------------------------
 Writes to the packet properties string safely.
 
	msg:	Message to write.
	level:	Depth of message.
 
****************************************************/

void props_write(string msg, int level)
{
	boost::mutex::scoped_lock	the_lock(io_mutex);		//Restrict.
	
	if (level == 0)
		packet_props.append(msg + "\n");		//append, no tabs
	else 
	{
		packet_props.append(level,0x09);		//write tabs x level
		packet_props.append(msg + "\n");
	}

	//scoped_lock destructor releases the_lock.
}

